<?php
declare(strict_types=1);
namespace Jwt;
use Exception;

/**
 * Rsa 签名，验签
 */
/**
 * @method static generate_sign(array $params,  string $private_key): string
 * @method static verify_sign(array $params, string $sign, string $public_key): bool
 */
class Rsa{
    /**
     * @var int
     */
	public int $algorithm = OPENSSL_ALGO_SHA256;

    /**
     * @var string
     */
	public string $post_charset = 'UTF-8';

    /**
     * @var string
     */
	private string $file_charset = "UTF-8";

    /**
     * 生成签名
     * @param array $params
     * @param string $private_key
     * @return string
     * @throws Exception
     */
    private  function generate_sign(array $params,  string $private_key): string
    {
		return $this->sign($this->get_sign_content($params), $private_key);
	}

    /**
     * 验证签名
     * @param array $params
     * @param string $sign
     * @param string $public_key
     * @return bool
     * @throws Exception
     */
    private function verify_sign(array $params, string $sign, string $public_key): bool
    {
		return $this->verify($this->get_sign_content($params), $sign, $public_key);
	}

    /**
     * 签名
     * @param string $data
     * @param string $private_key
     * @return string
     * @throws Exception
     */
    private  function sign(string $data, string $private_key): string
    {
        $privateKey = openssl_pkey_get_private($private_key);
		($privateKey) or throw new Exception('您使用的私钥格式错误，请检查RSA私钥配置');
		openssl_sign($data, $sign, $privateKey, $this->algorithm);
        return base64_encode($sign);
	}

    /**
     * 获取签名字符串
     * @param array $params
     * @return string
     */
    private  function get_sign_content(array $params): string
    {
		ksort($params);
		$string_to_be_signed = "";
		$i = 0;
		foreach ($params as $k => $v) {
			if (false === $this->check_empty((string)$v) && !str_starts_with((string)$v, "@")) {
				// 转换成目标字符集
				$v = $this->charset((string)$v, $this->post_charset);
				if ($i == 0) {
					$string_to_be_signed .= "$k" . "=" . "$v";
				} else {
					$string_to_be_signed .= "&" . "$k" . "=" . "$v";
				}
				$i++;
			}
		}
		unset($k, $v);
		return $string_to_be_signed;
	}

	/**
	 * 校验$value是否非空
	 *  if not set ,return true;
	 *    if is null , return true;
	 **/
    private  function check_empty(string $value): bool
    {
		if(!isset($value)) return true;
        if(trim($value) === "") return true;
		return false;
	}

    /**
     * 转换字符集编码
     * @param string $data
     * @param string $target_charset
     * @return string
     */
    private  function charset(string $data, string $target_charset): string
    {
		if(!empty($data)) {
			$file_type = $this->file_charset;
			if(strcasecmp($file_type, $target_charset) != 0) {
				$data = mb_convert_encoding($data, $target_charset, $file_type);
			}
		}
		return $data;
	}

    /**
     * 验签
     * @param string $data
     * @param string $sign
     * @param string $public_key
     * @return boolean
     * @throws Exception
     */
    private  function verify(string $data, string $sign, string $public_key): bool
    {
        $res = openssl_pkey_get_public($public_key);
        ($res) or throw new Exception('RSA公钥错误。请检查公钥文件格式是否正确');
        return (bool)openssl_verify($data, base64_decode($sign), $res, $this->algorithm);
	}

    /**
     * Handle dynamic, static calls to the object.
     *
     * @param string $method
     * @param array $args
     * @return mixed
     *
     * @throws RuntimeException
     */
    public static function __callStatic(string $method, array $args)
    {
        $instance = new self();
        return $instance->$method(...$args);
    }
}
